﻿using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace WordpressRestApi
{
    /// <summary>Client for the Post API.</summary>
    /// <remarks>See https://developer.wordpress.com/docs/api/ for the endpoints available.</remarks>
    public class PostClient : ApiClientBase
    {
        #region Inner Types
        private class PostPayload
        {
            [JsonProperty("found")]
            public int Found { get; set; }

            [JsonProperty("posts")]
            public PostInfo[] Posts { get; set; }

            [JsonProperty("meta")]
            public MetaInfo Meta { get; set; }
        }

        private class MetaInfo
        {
            [JsonProperty("next_page")]
            public string NextPage { get; set; }
        }
        #endregion

        private static readonly IDictionary<PostStatus, string> _statusMap;
        private static readonly IDictionary<PostField, string> _fieldMap;
        private readonly string _postUrlRoot;

        static PostClient()
        {
            _statusMap = CreateStatusMap();
            _fieldMap = CreateFieldMap();
        }

        /// <summary>Constructor.</summary>
        /// <param name="token"></param>
        internal PostClient(TokenInfo token)
            : base(token)
        {
            _postUrlRoot = API_ROOT + "sites/" + Token.BlogID + "/posts";
        }

        /// <summary>Returns all the posts of the associated site.</summary>
        /// <remarks>See https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/ </remarks>
        /// <returns>Asynchronous enumerable of posts.</returns>
        public IAsyncEnumerable<PostInfo> GetAllPosts()
        {
            return GetPosts(new PostParametrization());
        }

        /// <summary>Returns all the posts of the associated site fitting the parametrization.</summary>
        /// <param name="parametrization">Parametrization on the post query.</param>
        /// <remarks>See https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/ </remarks>
        /// <returns>Asynchronous enumerable of posts.</returns>
        public IAsyncEnumerable<PostInfo> GetPosts(PostParametrization parametrization)
        {
            var startUrl = CreateFilter(_postUrlRoot, parametrization);

            return new ApiAsyncEnumerable<PostInfo, PostPayload>(
                Token,
                startUrl,
                (payload) => payload.Posts,
                (payload, page) => !string.IsNullOrWhiteSpace(payload.Meta.NextPage),
                (payload, page) => startUrl + "&page_handle=" + Uri.EscapeDataString(payload.Meta.NextPage));
        }

        /// <summary>Update the tags of a posts.</summary>
        /// <param name="postID">The ID of the Post.</param>
        /// <param name="tagSlugs">The list of tag slugs the post should have.</param>
        /// <returns>The updated post.</returns>
        public async Task<PostInfo> UpdateTagsAsync(int postID, IEnumerable<string> tagSlugs)
        {
            if (postID <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(postID), "Must be a valid (greater than zero) ID");
            }

            var tagCsv = tagSlugs == null
                ? string.Empty
                : string.Join(",", tagSlugs);
            var url = _postUrlRoot + "/" + postID;
            var updatedPost = await PostRequestAsync<PostInfo>(url, Tuple.Create("tags", tagCsv));

            return updatedPost;
        }

        private static IDictionary<PostStatus, string> CreateStatusMap()
        {
            var values = Enum.GetValues(typeof(PostStatus));
            var fields = from v in values.Cast<PostStatus>()
                         select new { Value = v, Field = typeof(PostStatus).GetRuntimeField(v.ToString()) };
            var mapArray = from f in fields
                           select new { Value = f.Value, JsonName = GetPropertyName(f.Field) };
            var map = mapArray.ToDictionary(p => p.Value, p => p.JsonName);

            return map;
        }

        private static IDictionary<PostField, string> CreateFieldMap()
        {
            var values = Enum.GetValues(typeof(PostField));
            var properties = from v in values.Cast<PostField>()
                             select new { Value = v, Property = typeof(PostInfo).GetRuntimeProperty(v.ToString()) };
            var mapArray = from p in properties
                           select new { Value = p.Value, JsonName = GetPropertyName(p.Property) };
            var map = mapArray.ToDictionary(p => p.Value, p => p.JsonName);

            return map;
        }

        private static string GetPropertyName(MemberInfo m)
        {
            var attribute = (from a in m.CustomAttributes
                             where a.AttributeType == typeof(JsonPropertyAttribute)
                             select a).FirstOrDefault();

            return attribute == null
                ? m.Name
                : attribute.ConstructorArguments.First().Value.ToString();
        }

        private static string CreateFilter(string stemUrl, PostParametrization parametrization)
        {
            var builder = new StringBuilder(stemUrl);

            builder.Append('?');
            CreateNumberFilter(builder, parametrization.Number);
            CreateStatusFilter(builder, parametrization.Status);
            CreatePostTypeFilter(builder, parametrization.PostType);
            CreateFieldsFilter(builder, parametrization.Fields);
            CreateOrderByFilter(builder, parametrization.OrderByAscending, parametrization.OrderByField);
            CreateTagFilter(builder, parametrization.TagSlug);

            return builder.ToString();
        }

        private static void CreateTagFilter(StringBuilder builder, string tagSlug)
        {
            if (!string.IsNullOrWhiteSpace(tagSlug))
            {
                builder.Append("&tag=");
                builder.Append(tagSlug.Trim());
            }
        }

        private static void CreateOrderByFilter(StringBuilder builder, bool orderByAscending, PostField? orderByField)
        {
            if (orderByField != null)
            {
                builder.Append("&order=");
                builder.Append(orderByAscending ? "ASC" : "DESC");
                builder.Append("&order_by=");
                builder.Append(_fieldMap[orderByField.Value]);
            }
        }

        private static void CreateNumberFilter(StringBuilder builder, int? number)
        {
            builder.Append("&number=");

            if (number == null)
            {
                builder.Append("100");
            }
            else
            {
                builder.Append(number.Value.ToString());
            }
        }

        private static void CreateFieldsFilter(StringBuilder builder, IEnumerable<PostField> fields)
        {
            if (fields != null && fields.Any())
            {
                var jsonNames = from f in fields
                                select _fieldMap[f];

                builder.Append("&fields=");
                builder.Append(string.Join(",", jsonNames));
            }
        }

        private static void CreatePostTypeFilter(StringBuilder builder, string postType)
        {
            if (!string.IsNullOrWhiteSpace(postType))
            {
                builder.Append("&type=");
                builder.Append(postType);
            }
        }

        private static void CreateStatusFilter(StringBuilder builder, PostStatus? status)
        {
            builder.Append("&status=");

            if (status == null)
            {
                builder.Append("any");
            }
            else
            {
                builder.Append(_statusMap[status.Value]);
            }
        }
    }
}